<!-- 页面打印组件 -->
<template>
	<span @click="handlePrint">
		<slot>
			<el-button>打印</el-button>
		</slot>
	</span>
</template>

<script setup name="MyPrint">
import { addClass, removeClass, setStyle } from 'element-plus/es/utils/dom/style';
/**
 * MyPrint
 * @module components/MyPrint
 * @example
 *
 * // 使用说明
 *  <my-print print-range=".pring-range1">
 *  </my-print>
 *
 * // ----------或使用slot-------
 *  <my-print print-range=".pring-range1">
 *    <el-button type="primary" icon="Tickets" #button></el-button>
 *  </my-print>
 */
/**
 * 作用域插槽
 * @member slot
 * @property {string} [button] 自定义按钮
 */
/**
 * 属性参数
 * @member
 * @property {String} [printRange] 打印区域，css选择器，该参数为必须
 * @property {String} [containerClass = 'my-print-page-container'] 打印内容的容器className
 * @property {Function | HTMLElement | String} [parentDom] 作为父容器的dom对象，或者返回dom对象的方法,dom中必须包含container-class指定的className
 * @property {Object} [rootStyle] 顶层容器的样式,请不要设置position为absolute,relative或static, 否则分页效果会不起作用
 * @property {Function} [beforePrint] 在复制打印区域前执行，须返回promise对象
 * @property {Function} [afterPrint] 在打印完成之后执行
 * @property {String} [removeSelector] 打印时需要删除的元素选择器
 * @property {Array} [styleArray] 打印时须改变的样式数组，示例:[{selector: '.selector', style: {height: 'auto'}}]
 * @property {Function} [domTransfer] 自定义修改打印内容的方法
 * @property {Boolean} [disableDefaultStyle] 禁用默认样式数组，只使用style-array参数
 * @property {Boolean} [preview] 打印后不删除页面打印内容
 */
const props = defineProps({
	printRange: {
		type: String,
		default: '',
		required: true
	},
	containerClass: {
		type: String,
		default: 'my-print-page-container'
	},
	parentDom: [Function, String],
	rootStyle: {
		type: Object,
		default: () => ({})
	},
	beforePrint: Function,
	afterPrint: Function,
	removeSelector: {
		type: String,
		default: ''
	},
	styleArray: {
		type: Array,
		default: () => []
	},
	domTransfer: Function,
	disableDefaultStyle: Boolean,
	preview: Boolean
});
// 处理打印时表格滚动显示的问题
const defaultStyles = [
	{ selector: '.el-table', style: { height: 'auto' } },
	{ selector: '.el-table__inner-wrapper', style: { height: 'auto' } }
];
let handle = null,
	$tDoms = null;

function handlePrint() {
	if (props.beforePrint) {
		props.beforePrint().then(cloneDom);
	} else {
		cloneDom();
	}
}
function domTransferDefault(tDom) {
	// 顶层容器默认的样式，请不要设置position为absolute,relative或static, 否则分页效果会不起作用
	let style = {
		width: '100%',
		'max-width': '1200px',
		'background-color': '#fff',
		padding: '0 80px 0 0',
		'box-sizing': 'border-box',
		...props.rootStyle
	};
	setStyle(tDom, style);
	// 在打印之前可能需要删除某些元素，可通过removeSelector传入选择器将其删除
	if (props.removeSelector) {
		const removeDoms = tDom.querySelectorAll(props.removeSelector);
		removeDoms.forEach((item) => item.parentNode.removeChild(item));
	}
	// 在打印之前可能需要改变某些元素的样式，可以将一个数组通过styleArray传进来
	// 数组格式为 [{selector: '.selector-name',style: {height: 'auto'}}]
	let selectors = props.disableDefaultStyle
		? props.styleArray
		: [...defaultStyles, ...props.styleArray, { selector: props.printRange, style: { height: 'auto' } }];
	// 迭代改变每个打印区域的每个dom元素

	selectors.forEach((selector) => {
		const elems = tDom.querySelectorAll(selector.selector);
		elems.forEach((elem) => setStyle(elem, selector.style));
	});

	// 如果使用者需要编写自己的转换逻辑，则使用domTransfer参数传递一个处理方法
	let tDomTransfered = props.domTransfer?.(tDom) || tDom;
	if (!tDomTransfered) throw new Error('domTransfer方法须返回正确的dom元素');

	return tDomTransfered;
}
function removePrintPreview() {
	// 将打印区域外的元素重新显示
	const rootElems = document.querySelectorAll('body>*');
	rootElems.forEach((elem) => {
		removeClass(elem, 'my-print-hide');
		removeClass(elem, 'my-print-displaynone');
	});
	// 删除打印区域
	document.body.removeChild($tDoms);
	$tDoms = null;
}
function createDom(innerHTML) {
	const con = document.createElement('div');
	con.innerHTML = innerHTML;
	return con.firstChild;
}
function prependDom(parent, elem) {
	parent.innerHTML = elem.outerHTML + parent.innerHTML;
	return parent;
}
function closeAfterPrint() {
	handle = document.execCommand('print');
	if (handle) {
		// 如果不保留打印预览，则直接删除打印区域
		removePrintPreview();
		props.afterPrint?.();
	} else {
		setTimeout(closeAfterPrint, 300);
	}
}
// 克隆Dom
function cloneDom() {
	if (!props.printRange) return;
	// 将打印区域筛选出来用一个div包裹起来
	const contents = document.querySelectorAll(props.printRange);
	const con = document.createElement('div');
	contents.forEach((content, index) => {
		const cloneElem = content.cloneNode(true);
		// 处理canvas元素，使用原来canvas对象的toDataURL方法生成base64,替换复制dom中的canvas
		const canvas = content.querySelectorAll('canvas');
		const canvasClone = cloneElem.querySelectorAll('canvas');
		canvas.forEach((canvas, i) => {
			const imgSrc = canvas.toDataURL();
			const img = document.createElement('img');
			img.src = imgSrc;
			canvasClone[i].parentNode.replaceChild(img, canvasClone[i]);
		});
		// 添加分页符样式，最后一页不加分页符，否则如果最后一页占满了，会多出一页空白页
		if (index !== contents.length - 1) addClass(cloneElem, 'my-print-page-break');

		// 将复制的打印区域添加到容器中
		con.appendChild(cloneElem);
	});
	// 使用一个容器再包裹，方便后面移除
	const tDom = document.createElement('div');

	// tDom.id = 'body-print-range';
	// 在顶层容器和打印内容之间可以添加DOM元素，但需指定打印内容所在的class名称
	if (props.parentDom) {
		let parentDom = getParentDom();
		if (!parentDom) throw new Error('没找到父容器，检查是否缺少container-class参数');
		const parentCon = parentDom.querySelector('.' + props.containerClass) || parentDom;

		parentCon.appendChild(con);
		tDom.appendChild(parentDom);
	} else {
		tDom.className = props.containerClass;
		tDom.appendChild(con);
	}

	const tDomTransfered = domTransferDefault(tDom);
	document.querySelectorAll('body>*').forEach((elem) => addClass(elem, 'my-print-hide'));
	$tDoms = tDomTransfered;

	// 打印内容添加到body下
	document.body.appendChild(tDomTransfered);

	// 添加打印预览头部
	const previewTitle = createDom(`<div class="my-print-preview-title my-print-hide">
                          <span class="title">打印预览</span>
                          <div class="my-print-preview-close" onclick="handlePrint()">打印</div>
                          <div class="my-print-preview-close" onclick="closePrintPreview()">关闭</div>
                      </div>`);
	prependDom(tDom, previewTitle);

	// 如果尚未注册打印方法，则注册一个
	window.closePrintPreview = removePrintPreview;
	window.handlePrint = print;
	// 隐藏除打印区域外的内容
	document.querySelectorAll('body>*').forEach((elem) => {
		if (elem !== tDom) {
			addClass(elem, 'my-print-hide');
			if (props.preview) addClass(elem, 'my-print-displaynone');
		}
	});
	addClass(tDom, 'my-print-visible');
	if (!props.preview) addClass(tDom, 'my-print-displaynone');

	// 如果不预览直接打印
	props.preview || print();
}

function getParentDom() {
	if (props.parentDom instanceof HTMLElement) return props.parentDom;
	if (typeof props.parentDom === 'string') return createDom(props.parentDom);
	if (typeof props.parentDom === 'function') return props.parentDom();
	throw new Error('parent-dom必须为dom元素');
}

function print() {
	if (props.printRange) {
		setTimeout(closeAfterPrint, 20);
	} else {
		window.print();
	}
}
defineExpose({
	handlePrint
});
</script>

<style lang="scss">
.my-print-preview-title {
	text-align: center;

	.title {
		font-size: 18px;
		font-weight: 600;
		line-height: 50px;
	}

	.my-print-preview-close {
		float: right;
		padding: 5px 10px;
		margin-top: 10px;
		margin-right: 20px;
		color: #888;
		cursor: pointer;
		border: 1px solid #ccc;
		border-radius: 5px;
	}
}

.my-print-displaynone {
	display: none;
}

@media print {
	@page {
		size: 0;
		margin: 0;

		/** 是否显示标题 和链接 ，不显示改为0 **/
	}

	.my-print-page-break {
		page-break-after: always;
	}

	.my-print-hide {
		display: none;
	}

	.my-print-visible {
		display: block;
	}
}
</style>
